﻿//
// © Algobox Ltd - Benn Eichhorn
//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Algobox.Objects.Finance.Orders;
using System.Threading;
using Algobox.Objects.Finance.Prices;

namespace Algobox.Structure.Finance.Strategies.SpikeArb
{
    public class SpikeArbLeg
    {
        #region Constructors

        public SpikeArbLeg(SpikeArbStock manager, SpikeArbStock.SpikeArbCollar collar, double percentage, double value, double window)
        {
            _priceBuy = 0;
            _priceSell = 0;
            Manager = manager;
            _value = value;
            _percentage = percentage;
            _window = window;
            Collar = collar;
            _listingPrimary = new SpikeArbLegListing(this, manager.ListingPrimary);
            _listingChix = new SpikeArbLegListing(this, manager.ListingChix);
            _listingBats = new SpikeArbLegListing(this, manager.ListingBats);

            _logId = manager.Stock.ToString() + "|" + Collar.ToString() + "|" + _value + "@" + _percentage + "%";
        }


        #endregion
        //////////
        #region Member Variables
        
        private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger();

        const double PRICE_BUFFER = 0.1;

        private readonly string _logId;

        bool _isEnabled = false;
        bool _isAuction = false;
        bool _isPaused = true;
        bool _isPausedPrimary = true;
        bool _isPausedChix = true;
        bool _isPausedBats = true;

        double _percentage;
        double _value;
        double _window;
        
        SpikeArbLegListing _listingPrimary;
        SpikeArbLegListing _listingChix;
        SpikeArbLegListing _listingBats;

        double _priceBuy;
        double _priceSell;
        uint _quantitySell;
        uint _quantityBuy;

        readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim();
        readonly object _lockAsk = new object();
        readonly object _lockBid = new object();

        #endregion
        //////////
        #region Properties

        public SpikeArbStock.SpikeArbCollar Collar { get; private set; }

        public SpikeArbStock Manager { get; private set; }

        public double Value { get { return _value; } }
        public double Percentage { get { return _percentage; } }
        public double Window { get { return _window; } }

        public bool IsReady { get { return _isEnabled && !_isPaused && (_listingPrimary.IsLegEnabled || _listingChix.IsLegEnabled || _listingBats.IsLegEnabled); } }

        public bool IsEnabled
        {
            get { return _isEnabled; }
            internal set
            {
                _locker.EnterUpgradeableReadLock();
                try
                {
                    if (!_isEnabled && value)
                    {
                        _locker.EnterWriteLock();
                        _priceBuy = 0;
                        _priceSell = 0;
                        _isEnabled = true;
                        _locker.ExitWriteLock();
                    }
                    else if (_isEnabled && !value)
                    {
                        StopStrategy();
                        _locker.EnterWriteLock();
                        _isEnabled = false;
                        _locker.ExitWriteLock();
                    }
                }
                finally
                {
                    _locker.ExitUpgradeableReadLock();
                }
            }
        }

        public bool IsAuction
        {
            get { return _isAuction; }
            internal set
            {
                _locker.EnterUpgradeableReadLock();
                try
                {
                    if (_isAuction && !value)
                    {
                        _locker.EnterWriteLock();
                        _priceBuy = 0;
                        _priceSell = 0;
                        _isAuction = false;
                        _locker.ExitWriteLock();
                    }
                    else if (!_isAuction && value)
                    {
                        _locker.EnterWriteLock();
                        _isAuction = true;
                        _locker.ExitWriteLock();
                        StopStrategy();
                    }
                }
                finally
                {
                    _locker.ExitUpgradeableReadLock();
                }
            }
        }

        public bool IsPaused
        {
            get { return _isPaused; }
            internal set
            {
                _locker.EnterUpgradeableReadLock();
                try
                {
                    if (_isPaused && !value)
                    {
                        _locker.EnterWriteLock();
                        _priceBuy = 0;
                        _priceSell = 0;
                        _isPaused = false;
                        _locker.ExitWriteLock();
                    }
                    else if (!_isPaused && value)
                    {
                        _locker.EnterWriteLock();
                        _isPaused = true;
                        _locker.ExitWriteLock();
                        StopStrategy();
                    }
                }
                finally
                {
                    _locker.ExitUpgradeableReadLock();
                }
            }
        }

        public bool IsPausedPrimary
        {
            get { return _isPausedPrimary; }
            internal set
            {
                _locker.EnterUpgradeableReadLock();
                try
                {
                    if (_isPausedPrimary && !value)
                    {
                        _locker.EnterWriteLock();
                        _priceBuy = 0;
                        _priceSell = 0;
                        _isPausedPrimary = false;
                        _locker.ExitWriteLock();
                    }
                    else if (!_isPausedPrimary && value)
                    {
                        _locker.EnterWriteLock();
                        _isPausedPrimary = true;
                        _locker.ExitWriteLock();
                        _listingPrimary.CancelBuyCollar();
                        _listingPrimary.CancelSellCollar();
                    }
                }
                finally
                {
                    _locker.ExitUpgradeableReadLock();
                }
            }
        }

        public bool IsPausedChix
        {
            get { return _isPausedChix; }
            internal set
            {
                _locker.EnterUpgradeableReadLock();
                try
                {
                    if (_isPausedChix && !value)
                    {
                        _locker.EnterWriteLock();
                        _priceBuy = 0;
                        _priceSell = 0;
                        _isPausedChix = false;
                        _locker.ExitWriteLock();
                    }
                    else if (!_isPausedChix && value)
                    {
                        _locker.EnterWriteLock();
                        _isPausedChix = true;
                        _locker.ExitWriteLock();
                        _listingChix.CancelBuyCollar();
                        _listingChix.CancelSellCollar();
                    }
                }
                finally
                {
                    _locker.ExitUpgradeableReadLock();
                }
            }
        }

        public bool IsPausedBats
        {
            get { return _isPausedBats; }
            internal set
            {
                _locker.EnterUpgradeableReadLock();
                try
                {
                    if (_isPausedBats && !value)
                    {
                        _locker.EnterWriteLock();
                        _priceBuy = 0;
                        _priceSell = 0;
                        _isPausedBats = false;
                        _locker.ExitWriteLock();
                    }
                    else if (!_isPausedBats && value)
                    {
                        _locker.EnterWriteLock();
                        _isPausedBats = true;
                        _locker.ExitWriteLock();
                        _listingBats.CancelBuyCollar();
                        _listingBats.CancelSellCollar();
                    }
                }
                finally
                {
                    _locker.ExitUpgradeableReadLock();
                }
            }
        }

        #endregion
        //////////
        #region Functions
        
        private void StopStrategy()
        {
            _listingPrimary.CancelBuyCollar();
            _listingPrimary.CancelSellCollar();
            _listingChix.CancelBuyCollar();
            _listingChix.CancelSellCollar();
            _listingBats.CancelBuyCollar();
            _listingBats.CancelSellCollar();
        }

        public override string ToString()
        {
            return Collar.ToString() + " " + Value + "@" + Percentage + "%";
        }

        internal void ChangeExits(SpikeArbStock.ExitPercentages value)
        {
            _listingPrimary.ChangeExits(value);
            _listingChix.ChangeExits(value);
            _listingBats.ChangeExits(value);
        }

        internal void SetCollar(double percentage, double value, double window)
        {
            StopStrategy();

            _locker.EnterWriteLock();

            _isEnabled = false;
            _percentage = percentage;
            _value = value;
            _window = window;
            
            _priceBuy = 0;
            _priceSell = 0;

            _locker.ExitWriteLock();
        }


        internal void OnPriceBid(Objects.Finance.Prices.PriceVolume bid)
        {
            if (Monitor.TryEnter(_lockBid))
            {
                try
                {
                    if (_locker.TryEnterReadLock(0))
                    {
                        try
                        {
                            if (_isEnabled && !_isAuction && !_isPaused)
                            {
                                // check buy order range
                                if (_priceBuy < bid.Price - (bid.Price * ((_percentage + _window) / 100))
                                    || _priceBuy > bid.Price - (bid.Price * ((_percentage - _window) / 100)))
                                {
                                    // outside range, change order
                                    if (Log.IsTraceEnabled)
                                    {
                                        double oldPrice = _priceBuy;
                                        _priceBuy = bid.Price - (bid.Price * _percentage / 100);

                                        if (Manager.Stock.Currency == Objects.Finance.Currencies.Currency.GBX)
                                            _quantityBuy = (uint)(_value / (_priceBuy / 100));
                                        else
                                            _quantityBuy = (uint)(_value / _priceBuy);

                                        Log.Trace("{0}|OnPriceBid Buy price out of range changing from {1} to {2} with quantity {3}", _logId, oldPrice, _priceBuy, _quantityBuy);
                                    }
                                    else
                                    {
                                        _priceBuy = bid.Price - (bid.Price * _percentage / 100);

                                        if (Manager.Stock.Currency == Objects.Finance.Currencies.Currency.GBX)
                                            _quantityBuy = (uint)(_value / (_priceBuy / 100));
                                        else
                                            _quantityBuy = (uint)(_value / _priceBuy);
                                    }

                                    // add order with quantity salt of plus or minus 10
                                    if (_listingPrimary.IsLegEnabled && !_isPausedPrimary)
                                        _listingPrimary.AddOrUpdateBuy(bid.Price, _priceBuy, _quantityBuy);
                                    if (_listingChix.IsLegEnabled && !_isPausedChix)
                                        _listingChix.AddOrUpdateBuy(bid.Price, _priceBuy, _quantityBuy);
                                    if (_listingBats.IsLegEnabled && !_isPausedBats)
                                        _listingBats.AddOrUpdateBuy(bid.Price, _priceBuy, _quantityBuy);
                                                              
                                }
                                else
                                {
                                    if (_priceBuy == 0)
                                    {
                                        // no price set
                                        _priceBuy = bid.Price - (bid.Price * _percentage / 100);

                                        if (Manager.Stock.Currency == Objects.Finance.Currencies.Currency.GBX)
                                            _quantityBuy = (uint)(_value / (_priceBuy / 100));
                                        else
                                            _quantityBuy = (uint)(_value / _priceBuy);

                                        if (Log.IsTraceEnabled)
                                        {
                                            Log.Trace("{0}|OnPriceBid No buy price set so adding order of {1}@{2}", _logId, _quantityBuy, _priceBuy);
                                        }  

                                        if (_listingPrimary.IsLegEnabled && !_isPausedPrimary)
                                            _listingPrimary.AddOrUpdateBuy(bid.Price, _priceBuy, _quantityBuy);
                                        if (_listingChix.IsLegEnabled && !_isPausedChix)
                                            _listingChix.AddOrUpdateBuy(bid.Price, _priceBuy, _quantityBuy);
                                        if (_listingBats.IsLegEnabled && !_isPausedBats)
                                            _listingBats.AddOrUpdateBuy(bid.Price, _priceBuy, _quantityBuy);
                                    }
                                    else
                                    {
                                        if (_listingPrimary.IsLegEnabled && _listingPrimary.IsEnabled && !_listingPrimary.IsActiveOrderEntryBuy && !_isPausedPrimary)
                                        {
                                            if (Log.IsTraceEnabled)
                                            {
                                                Log.Trace("{0}|OnPriceBid Primary buy collar enabled adding new order of {1}@{2}", _logId, _quantityBuy, _priceBuy);
                                            }  
                                            _listingPrimary.AddOrUpdateBuy(bid.Price, _priceBuy, _quantityBuy);
                                        }
                                        if (_listingChix.IsLegEnabled && _listingChix.IsEnabled && !_listingChix.IsActiveOrderEntryBuy && !_isPausedChix)
                                        {
                                            if (Log.IsTraceEnabled)
                                            {
                                                Log.Trace("{0}|OnPriceBid Chix buy collar enabled adding new order of {1}@{2}", _logId, _quantityBuy, _priceBuy);
                                            }  
                                            _listingChix.AddOrUpdateBuy(bid.Price, _priceBuy, _quantityBuy);
                                        }
                                        if (_listingBats.IsLegEnabled && _listingBats.IsEnabled && !_listingBats.IsActiveOrderEntryBuy && !_isPausedBats)
                                        {
                                            if (Log.IsTraceEnabled)
                                            {
                                                Log.Trace("{0}|OnPriceBid Bats buy collar enabled adding new order of {1}@{2}", _logId, _quantityBuy, _priceBuy);
                                            }  
                                            _listingBats.AddOrUpdateBuy(bid.Price, _priceBuy, _quantityBuy);
                                        }
                                    }
                                }
                            }
                        }
                        finally
                        {
                            _locker.ExitReadLock();
                        }
                    }
                }
                finally
                {
                    Monitor.Exit(_lockBid);
                }
            }
        }


        internal void OnPriceAsk(Objects.Finance.Prices.PriceVolume ask)
        {
            if (Monitor.TryEnter(_lockAsk))
            {
                try
                {
                    if (_locker.TryEnterReadLock(0))
                    {
                        try
                        {
                            if (_isEnabled && !_isAuction && !_isPaused)
                            {
                                // check sell order range
                                if (_priceSell < ask.Price + (ask.Price * ((_percentage - _window) / 100))
                                    || _priceSell > ask.Price + (ask.Price * ((_percentage + _window) / 100)))
                                {
                                    // outside range, change order
                                    if (Log.IsTraceEnabled)
                                    {
                                        double oldPrice = _priceSell;
                                        _priceSell = ask.Price + (ask.Price * _percentage / 100);

                                        if (Manager.Stock.Currency == Objects.Finance.Currencies.Currency.GBX)
                                            _quantitySell = (uint)(_value / (_priceSell / 100));
                                        else
                                            _quantitySell = (uint)(_value / _priceSell);

                                        Log.Trace("{0}|OnPriceAsk Sell price out of range changing from {1} to {2} with quantity {3}", _logId, oldPrice, _priceSell, _quantitySell);
                                    }
                                    else
                                    {
                                        _priceSell = ask.Price + (ask.Price * _percentage / 100);

                                        if (Manager.Stock.Currency == Objects.Finance.Currencies.Currency.GBX)
                                            _quantitySell = (uint)(_value / (_priceSell / 100));
                                        else
                                            _quantitySell = (uint)(_value / _priceSell);
                                    }

                                    // add order with quantity salt of plus or minus 10
                                    if (_listingPrimary.IsLegEnabled && !_isPausedPrimary)
                                        _listingPrimary.AddOrUpdateSell(ask.Price, _priceSell, _quantitySell);
                                    if (_listingChix.IsLegEnabled && !_isPausedChix)
                                        _listingChix.AddOrUpdateSell(ask.Price, _priceSell, _quantitySell);
                                    if (_listingBats.IsLegEnabled && !_isPausedBats)
                                        _listingBats.AddOrUpdateSell(ask.Price, _priceSell, _quantitySell);
                                }
                                // see if we can add order for the first time in case it get's missed above
                                else
                                {
                                    if (_priceSell == 0)
                                    {
                                        // get new price
                                        _priceSell = ask.Price + (ask.Price * _percentage / 100);

                                        // get new quantity
                                        if (Manager.Stock.Currency == Objects.Finance.Currencies.Currency.GBX)
                                            _quantitySell = (uint)(_value / (_priceSell / 100));
                                        else
                                            _quantitySell = (uint)(_value / _priceSell);
                                        
                                        if (Log.IsTraceEnabled)
                                        {
                                            Log.Trace("{0}|OnPriceAsk No sell price set so adding order of {1}@{2}", _logId, _quantitySell, _priceSell);
                                        } 

                                        if (_listingPrimary.IsLegEnabled && !_isPausedPrimary)
                                            _listingPrimary.AddOrUpdateSell(ask.Price, _priceSell, _quantitySell);
                                        if (_listingChix.IsLegEnabled && !_isPausedChix)
                                            _listingChix.AddOrUpdateSell(ask.Price, _priceSell, _quantitySell);
                                        if (_listingBats.IsLegEnabled && !_isPausedBats)
                                            _listingBats.AddOrUpdateSell(ask.Price, _priceSell, _quantitySell);
                                    }
                                    else
                                    {
                                        if (_listingPrimary.IsLegEnabled && _listingPrimary.IsEnabled && !_listingPrimary.IsActiveOrderEntrySell && !_isPausedPrimary)
                                        {
                                            if (Log.IsTraceEnabled)
                                            {
                                                Log.Trace("{0}|OnPriceAsk Primary sell collar enabled adding new order of {1}@{2}", _logId, _quantitySell, _priceSell);
                                            }  
                                            _listingPrimary.AddOrUpdateSell(ask.Price, _priceSell, _quantitySell);
                                        }
                                        if (_listingChix.IsLegEnabled && _listingChix.IsEnabled && !_listingChix.IsActiveOrderEntrySell && !_isPausedChix)
                                        {
                                            if (Log.IsTraceEnabled)
                                            {
                                                Log.Trace("{0}|OnPriceAsk Chix sell collar enabled adding new order of {1}@{2}", _logId, _quantitySell, _priceSell);
                                            }  
                                            _listingChix.AddOrUpdateSell(ask.Price, _priceSell, _quantitySell);
                                        }
                                        if (_listingBats.IsLegEnabled && _listingBats.IsEnabled && !_listingBats.IsActiveOrderEntrySell && !_isPausedBats)
                                        {
                                            if (Log.IsTraceEnabled)
                                            {
                                                Log.Trace("{0}|OnPriceAsk Bats sell collar enabled adding new order of {1}@{2}", _logId, _quantitySell, _priceSell);
                                            }  
                                            _listingBats.AddOrUpdateSell(ask.Price, _priceSell, _quantitySell);
                                        }
                                    }
                                }
                            }
                        }
                        finally
                        {
                            _locker.ExitReadLock();
                        }
                    }
                }
                finally
                {
                    Monitor.Exit(_lockAsk);
                }
            }
        }


        #endregion


    }
}
